嗨嗨!大家好!歡迎回到 Rust 三十天挑戰的第二天!
昨天我們成功跑出了第一個 Rust 程式,不知道大家有沒有嘗試來完成那個小挑戰呢?如果還沒有,別擔心,今天我們也寫到昨天的小挑戰內容,之後你如果願意開始做會更容易!
今天要來探討 Rust 的變數、資料型別和常數。對於習慣了 C# 或其他高階語言的我們來說,Rust 的型別系統既熟悉又陌生。熟悉的是它同樣是強型別語言,陌生的是Rust語言本身對於變數的可變性(mutability)有著非常嚴格的要求。
其實,剛開始接觸這些概念時我也有點不習慣。畢竟在 C# 中,我們很少需要特別思考變數是否可變,雖然在clean code 有提到說,後天改值,是一個code smell,所以在開發過程中就要很依賴開發人員的潔癖程度,但在 Rust 中,這卻是一個核心概念。這也是我對於它特別有興趣的一個原因!
let
在 Rust 中,我們使用 let
關鍵字來宣告變數。讓我們先來看一個簡單的例子:
fn main() {
let x = 5;
println!("x 的值是:{}", x);
}
這看起來很簡單對吧?但這裡有個重要的概念:Rust 的變數預設是不可變的(immutable)!
如果我們嘗試修改 x
的值:
fn main() {
let x = 5;
println!("x 的值是:{}", x);
x = 6; // 這行會編譯錯誤!
println!("x 的值是:{}", x);
}
編譯器會很不客氣地告訴你:
error[E0384]: cannot assign twice to immutable variable `x`
mut
關鍵字如果我們真的需要修改變數的值,就必須明確地使用 mut
關鍵字:
fn main() {
let mut x = 5;
println!("x 的值是:{}", x);
x = 6; // 現在這樣就可以了!
println!("x 的值是:{}", x);
}
你可能會想:「這樣不是很麻煩嗎?為什麼不讓變數預設就可以修改?」
這個設計背後的哲學很有趣:Rust 鼓勵我們優先使用不可變的變數。這樣做有幾個好處:
mut
的變數,你就知道它在整個作用域內不會改變Rust 還有一個有趣的特性叫做「變數遮蔽」,允許我們重新宣告同名的變數:
fn main() {
let x = 5;
println!("第一個 x:{}", x);
let x = x + 1; // 用新的值遮蔽原本的 x
println!("第二個 x:{}", x);
let x = "現在 x 是字串了!"; // 甚至可以改變型別
println!("第三個 x:{}", x);
}
這和使用 mut
不同。Shadowing 實際上是建立了一個新的變數,只是恰好使用了相同的名字。這樣我們甚至可以改變變數的型別,這在 mut
變數中是不被允許的。
Rust 是一個靜態型別語言,這意味著所有變數的型別都必須在編譯時確定。不過別擔心,Rust 的型別推導(type inference)功能非常強大,很多時候我們不需要明確指定型別。
純量型別代表單一的值,Rust 有四種主要的純量型別:
Rust 提供了多種不同大小的整數型別:
長度 | 有號 | 無號 |
---|---|---|
8-bit | i8 |
u8 |
16-bit | i16 |
u16 |
32-bit | i32 |
u32 |
64-bit | i64 |
u64 |
128-bit | i128 |
u128 |
arch | isize |
usize |
fn main() {
let small_number: u8 = 255;
let big_number: i64 = -9223372036854775808;
let arch_dependent: usize = 42; // 在 64-bit 系統上相當於 u64
// Rust 會自動推導型別,預設是 i32
let default_integer = 42;
println!("各種數字:{}, {}, {}, {}",
small_number, big_number, arch_dependent, default_integer);
}
你也可以用不同的進位制來表示數字:
fn main() {
let decimal = 98_222; // 十進位,可以用 _ 增加可讀性
let hex = 0xff; // 十六進位
let octal = 0o77; // 八進位
let binary = 0b1111_0000; // 二進位
let byte = b'A'; // 位元組(只適用於 u8)
println!("各種表示法:{}, {}, {}, {}, {}",
decimal, hex, octal, binary, byte);
}
Rust 有兩種浮點數型別:f32
和 f64
(預設):
fn main() {
let pi: f32 = 3.14159; // 單精度
let e = 2.718281828; // 雙精度(預設)
println!("π ≈ {}, e ≈ {}", pi, e);
}
布林型別只有兩個可能的值:true
和 false
:
fn main() {
let is_rust_awesome = true;
let is_learning_fun: bool = false; // 明確指定型別
println!("Rust 很棒嗎?{}", is_rust_awesome);
println!("學習有趣嗎?{}", is_learning_fun);
}
Rust 的 char
型別代表一個 Unicode 純量值,使用單引號:
fn main() {
let letter = 'A';
let emoji = '😊';
let chinese = '中';
println!("字元:{}, {}, {}", letter, emoji, chinese);
}
複合型別可以將多個值組合成一個型別。Rust 有兩種原始的複合型別:
元組可以將不同型別的值組合在一起:
fn main() {
let person: (String, i32, bool) = (String::from("Alice"), 25, true);
// 解構賦值
let (name, age, is_student) = person;
println!("{} 今年 {} 歲,是學生:{}", name, age, is_student);
// 也可以用索引存取
let another_tuple = ("Bob", 30, false);
println!("第一個元素:{}", another_tuple.0);
println!("第二個元素:{}", another_tuple.1);
println!("第三個元素:{}", another_tuple.2);
}
陣列中的所有元素必須是相同型別,且長度固定:
fn main() {
let numbers = [1, 2, 3, 4, 5];
let months = ["一月", "二月", "三月", "四月", "五月"];
// 明確指定型別和長度
let zeros: [i32; 5] = [0; 5]; // 建立包含 5 個 0 的陣列
println!("第一個數字:{}", numbers[0]);
println!("第三個月份:{}", months[2]);
println!("零陣列:{:?}", zeros); // {:?} 用於除錯輸出
}
注意:如果你想要動態大小的陣列,你需要使用 Vec<T>
(我們之後會學到)。
除了變數,Rust 還提供了常數(constants),使用 const
關鍵字宣告:
const MAX_USERS: u32 = 100_000;
const PI: f64 = 3.14159265359;
fn main() {
println!("最大用戶數:{}", MAX_USERS);
println!("圓周率:{}", PI);
}
常數和不可變變數的差異:
mut
:常數永遠是不可變的有時候我們需要明確指定變數的型別,或者進行型別轉換:
fn main() {
// 型別標註
let x: i32 = 42;
let y: f64 = 3.14;
// 型別轉換(casting)
let integer = 42;
let float = integer as f64;
println!("整數:{},浮點數:{}", integer, float);
// 字串轉數字(記得昨天的小挑戰嗎?)
let number_string = "42";
let number: i32 = number_string.parse().expect("無法解析數字");
println!("從字串解析的數字:{}", number);
}
讓我們用今天學到的知識來改進昨天的小挑戰:這個程式展示了今天學到的幾個重要概念:
parse()
將字串轉換為數字if
在 Rust 中是表達式,可以回傳值mut
),有些不可變今天我們學到了:
變數與可變性:
mut
關鍵字建立可變變數資料型別:
常數:
const
宣告明天我們將繼續深入 Rust 的世界,學習函式和流程控制。我們會發現 Rust 中許多看似普通的語法,其實都隱藏著很有趣的設計!
試著建立一個程式,要求使用者輸入三個數字,然後:
提示:你可能需要用到 f64
型別來計算平均值,以及 %
運算子來判斷偶數!
如果在實作過程中遇到任何問題,歡迎在留言區討論。型別系統剛開始可能會覺得有點嚴格,但相信我,等你習慣了之後,你會愛上這種「編譯器幫你檢查錯誤」的感覺!
我們明天見!